#include "MainWindow.h"

#include "model/UMLDiagram.h"
#include "controller/DiagramFam.h"

#include <QMenu>
#include <QMenuBar>
#include <QDockWidget>
#include <QTextEdit>
#include <QApplication> // for quitting the app
#include <QFileDialog>
#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent)
:	QMainWindow(parent),
	 // _undo will be initialized by createMenus()
	 // _redo will be initialized by createMenus()
	 // _textEdit will be initialized by createTextEdit()
	_dman(this), _tman(_dman, this),  // safe because they take QWidget*, base
	 // _modifyTextCmd default is OK
	_acceptorObs(this) // safe, see function doc
{
	/* Create UI elements */

	setCentralWidget(_dman.display());

	createMenus();
	createPalette();
	createTextEdit();

	setWindowIcon(QIcon("icons/bumlet.xpm"));

	/* Hook up */

	connect(&_dman, SIGNAL(activeChanged(DiagramFam*)),
			SLOT(onActiveChanged(DiagramFam*)));
	connect(_textEdit, SIGNAL(textChanged()), SLOT(onTextChanged()));
	connect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)),
			SLOT(focusChanged(QWidget*, QWidget*)));

	/* Setup initial diagram */

	UMLDiagram* diagram = new UMLDiagram;

	_dman.add(diagram);
	diagram = NULL; // we don't own the pointer anymore
}

void MainWindow::newDiagram()
{
	_dman.add(new UMLDiagram);
}

void MainWindow::openDiagram()
{
	QString path = QFileDialog::getOpenFileName(this, tr("Open Diagram"),
			QString(),
			tr("bumlet diagrams (*.xml)"));

	try
	{
		if (!path.isEmpty())
			_dman.add(path);
	}
	catch (std::exception& ex)
	{
		QMessageBox::warning(this, "Bummer!", ex.what());
	}
}

void MainWindow::saveDiagram()
{
	_dman.active().save();
}

void MainWindow::saveDiagramAs()
{
	QString path = QFileDialog::getSaveFileName(this, tr("Save Diagram As"),
			QString(),
			tr("bumlet diagrams (*.xml)"));

	try
	{
		if (!path.isEmpty())
		{
			_dman.active().setPath(path);
			_dman.active().save();
		}
	}
	catch (std::exception& ex)
	{
		QMessageBox::warning(this, "Bummer!", ex.what());
	}
}

void MainWindow::onActiveChanged(DiagramFam* newfam)
{
	/* The logic here is that we stay connected to every DiagramFam* we've seen.
	 * We disconnect in two cases:
	 * 1) When connect, to prevent duplicate connections.
	 * 2) Implicitly, when a DiagramFam/UMLCanvas is destroyed.
	 *
	 * Staying connected to old UMLCanvases is fine because there's no way
	 * selection will be changed without user interaction.
	 */

	if (newfam)
	{
		// remove old connection(s)
		newfam->canvas().selection().disconnect(
				SIGNAL(changed(UMLObject*)), this);
		// restore it
		connect(&newfam->canvas().selection(),
				SIGNAL(changed(UMLObject*)), SLOT(edit(UMLObject*)));

		// same for AcceptorObserver
		newfam->acceptor().unattach(&_acceptorObs);
		newfam->acceptor().attach(&_acceptorObs);
	}

	refreshUndoRedo();

	// ALWAYS clear this to avoid dangling pointers.
	edit(NULL);
}

void MainWindow::edit(UMLObject* object)
{
	sendModifyTextCmd();

	if (object)
	{
		_textEdit->setPlainText(object->text().fullText());
		_modifyTextCmd.reset(
				new ModifyTextCmd(object, QString()));

		qDebug("Editing new object");
	}
	else
	{
		_textEdit->clear();
		_modifyTextCmd.reset();
	}
}

void MainWindow::onTextChanged()
{
	if (_modifyTextCmd.get())
	{
		_modifyTextCmd->setText(_textEdit->toPlainText());
		_dman.active().acceptor().acceptTransitive(*_modifyTextCmd);
	}
}

void MainWindow::focusChanged(QWidget* old, QWidget*)
{
	/* We just override to pick up focus out from the text edit. */
	if (old == _textEdit)
		sendModifyTextCmd();
}

void MainWindow::undo()
{
	_dman.active().acceptor().undo();
}

void MainWindow::redo()
{
	_dman.active().acceptor().redo();
}

MainWindow::AcceptorObs::AcceptorObs(MainWindow *context)
:	_context(context)
{
}

void MainWindow::AcceptorObs::update(Observable*)
{
	_context->refreshUndoRedo();
}

void MainWindow::createMenus()
{
	/* File menu */

	QMenu *menu = menuBar()->addMenu(tr("&File"));
	menu->addAction(tr("&New"), this, SLOT(newDiagram()),
			QKeySequence(QKeySequence::New));
	menu->addAction(tr("&Open"), this, SLOT(openDiagram()),
			QKeySequence(QKeySequence::Open));
	menu->addAction(tr("&Save"), this, SLOT(saveDiagram()),
			QKeySequence(QKeySequence::Save));
	menu->addAction(tr("Save &As..."), this, SLOT(saveDiagramAs()));
	// TODO: add keyboard shortcut
	menu->addAction(tr("E&xit"), qApp, SLOT(quit()));

	/* Edit menu */

	menu = menuBar()->addMenu(tr("&Edit"));

	_undo = menu->addAction(tr("&Undo"), this, SLOT(undo()),
			QKeySequence::Undo);
	_redo = menu->addAction(tr("&Redo"), this, SLOT(redo()),
			QKeySequence::Redo);
	refreshUndoRedo();
}

void MainWindow::createPalette()
{
	QDockWidget *dock = new QDockWidget(tr("Drawing Tools"), this);

	dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
	dock->setWidget(_tman.palette());

	// TODO: don't allow closing

	addDockWidget(Qt::RightDockWidgetArea, dock);
}

void MainWindow::createTextEdit()
{
	QDockWidget *dock = new QDockWidget(tr("Properties"), this);
	dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);

	_textEdit = new QTextEdit(dock);
	dock->setWidget(_textEdit);

	// TODO: don't allow closing
	
	addDockWidget(Qt::RightDockWidgetArea, dock);
}

void MainWindow::sendModifyTextCmd()
{
	if (_modifyTextCmd.get())
	{
		_modifyTextCmd->setText(_textEdit->toPlainText());
		_dman.active().acceptor().accept(
				std::auto_ptr<Command>(_modifyTextCmd));
	}
}

void MainWindow::refreshUndoRedo()
{
	if (_dman.isActive())
	{
		Acceptor& a = _dman.active().acceptor();
		_undo->setEnabled(a.canUndo());
		_redo->setEnabled(a.canRedo());
	}
	else
	{
		_undo->setEnabled(false);
		_redo->setEnabled(false);
	}
}

